﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DDD.Core {

    /// <summary>
    /// フレーム バッファー クラス
    /// </summary>
    /// <remarks>
    /// フレーム バッファーは描画対象となるカラー バッファー、デプス バッファー、ステンシルバッファーを
    /// 保存するコンテナ クラスです。
    /// システムはデフォルトのフレーム バッファーを持ち何もしなくても使用可能ですが、
    /// オフライン レンダリングのためにユーザーが独自のフレーム バッファーを作成する事があります。
    /// カラー バッファーには必ず2Dのテクスチャーを使用します。
    /// OpenGL のレンダー バッファーに相当するものは存在しません。
    /// フレーム バッファーには複数のカラー バッファーを設定可能です。
    /// これをマルチ レンダー ターゲットと言います。
    /// <note type="implement">
    /// 現状ステンシルバッファーは実装していません。ごめん
    /// </note>
    /// </remarks>
    public class FrameBuffer : Object {
        #region Field
        Image2D[] colorBuffers;
        Image2D[] depthBuffers;
        Image2D[] stencilBuffers;
        bool[] colorClears;
        bool[] depthClears;
        bool[] stencilClears;
        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <remarks>
        /// ユーザー独自のカスタム フレームバッファーを作成します。
        /// 初期値は以下のとおりです。
        /// <list type="bullet">
        ///   <item>カラー バッファーのクリア = すべて有効</item>
        ///   <item>デプス バッファーのクリア = 有効</item>
        ///   <item>ステンシル バッファーのクリア = 有効</item>
        ///   </list>
        /// </remarks>
        /// <param name="numColorBuffers">カラー バッファーの個数（0以上）</param>
        /// <param name="numDepthBuffer">デプス バッファーの個数（0または1）</param>
        /// <param name="numStencilBuffer">ステンシル バッファーの個数（0または1）</param>
        /// <exception cref="ArgumentException"><paramref name="numColorBuffer"/> &lt; 0</exception>
        /// <exception cref="ArgumentException"><paramref name="numDepthBuffer"/> &lt; 0 または <paramref name="numDepthBuffer"/> &gt; 1</exception>
        /// <exception cref="ArgumentException"><paramref name="numStencilBuffer"/> &lt; 0 または <paramref name="numStencilBuffer"/> &gt; 1</exception>
        public FrameBuffer (int numColorBuffers, int numDepthBuffer, int numStencilBuffer) {
            if (numColorBuffers < 0) {
                throw new ArgumentException ("Number of color buffer is invalid");
            }
            if (numDepthBuffer < 0 || numDepthBuffer > 1) {
                throw new ArgumentException ("Number of depth buffer is invalid");
            }
            if (numStencilBuffer < 0 || numStencilBuffer > 1) {
                throw new ArgumentException ("Number of stencil buffer is invalid");
            }

            this.colorBuffers = new Image2D[numColorBuffers];
            this.depthBuffers = new Image2D[numDepthBuffer];
            this.stencilBuffers = new Image2D[numStencilBuffer];

            this.colorClears = new bool[numColorBuffers];
            this.depthClears = new bool[numDepthBuffer];
            this.stencilClears = new bool[numStencilBuffer];
            for (var i = 0; i < colorClears.Length; i++) {
                this.colorClears[i] = true;
            }
            for (var i = 0; i < depthClears.Length; i++) {
                this.depthClears[i] = true;
            }
            for (var i = 0; i < stencilClears.Length; i++) {
                this.stencilClears[i] = true;
            }
        }
        #endregion

        #region Property
        /// <summary>
        /// カラー バッファーの個数
        /// </summary>
        /// <remarks>
        /// このフレーム バッファーに設定されたカラー バッファーの個数です。
        /// </remarks>
        public int ColorBufferCount {
            get { return colorBuffers.Length; }
        }

        /// <summary>
        /// デプス バッファーの個数
        /// </summary>
        /// <remarks>
        /// このフレーム バッファーに設定されたデプス バッファーの個数です。
        /// </remarks>
        public int DepthBufferCount {
            get { return depthBuffers.Length; }
        }

        /// <summary>
        /// ステンシル バッファーの総数
        /// </summary>
        /// <remarks>
        /// このフレーム バッファーに設定されたステンシル バッファーの個数です。
        /// </remarks>
        public int StencilBufferCount {
            get { return stencilBuffers.Length; }
        }

        /// <summary>
        /// カラー バッファーのクリアの有効･無効
        /// </summary>
        /// <remarks>
        /// レンダリングの開始前に0番目のカラー バッファーをクリアするかどうかのフラグです。
        /// クリア値はアタッチされた画像オブジェクトが保持しています。
        /// このプロパティは単一のカラー バッファーを持つフレーム バッファーの為に用意されています。
        /// i番目のカラー バッファーのクリアの有効無効を取得するには <see cref="GetColorClerar()"/> を利用してください。
        /// </remarks>
        /// <exception cref="InvalidOperationException"><see cref="DepthBufferCount"/> = 0</exception>
        /// <seealso cref="Image2D"/>
        /// <seealso cref="GetColorClear()"/>
        public bool ColorClear {
            get {
                if (ColorBufferCount == 0) {
                    throw new InvalidOperationException ("No color buffer");
                }
                return colorClears[0];
            }
        }

        /// <summary>
        /// デプス バッファーのクリアの有効･無効
        /// </summary>
        /// <remarks>
        /// レンダリングの開始前にデプス バッファーをクリアするかどうかのフラグです。
        /// クリア値はアタッチされた画像オブジェクトが保持しています。
        /// </remarks>
        /// <exception cref="InvalidOperationException"><see cref="DepthBufferCount"/> = 0</exception>
        /// <seealso cref="Image2D"/>
        public bool DepthClear {
            get {
                if (DepthBufferCount == 0) {
                    throw new InvalidOperationException ("No depth buffer");
                }
                return depthClears[0];
            }
        }

        /// <summary>
        /// ステンシル バッファーのクリアの有効･無効
        /// </summary>
        /// <remarks>
        /// レンダリングの開始前にステンシル バッファーをクリアするかどうかのフラグです。
        /// クリア値はアタッチされた画像オブジェクトが保持しています。
        /// </remarks>
        /// <exception cref="InvalidOperationException"><see cref="StencilBufferCount"/> = 0</exception>
        /// <seealso cref="Image2D"/>
        public bool StencilClear {
            get {
                if (StencilBufferCount == 0) {
                    throw new InvalidOperationException ("No stencil buffer");
                }
                return stencilClears[0];
            }
        }

        /// <summary>
        /// カラー バッファーを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// このフレーム バッファーにアタッチされたカラー バッファーを列挙します。
        /// 列挙子中に <c>null</c> は含まれません。
        /// </remarks>
        /// <seealso cref="ColorBufferCount"/>
        public IEnumerable<Image2D> ColorBuffers {
            get { return colorBuffers.Where (x => x != null); }
        }

        /// <summary>
        /// デプス バッファーを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// このフレーム バッファーにアタッチされたデプス バッファーを列挙します。
        /// 列挙子中に <c>null</c> は含まれません。
        /// </remarks>
        /// <seealso cref="DepthBufferCount"/>
        public IEnumerable<Image2D> DepthBuffers {
            get { return depthBuffers.Where (x => x != null); }
        }

        /// <summary>
        /// ステンシル バッファーを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// このフレーム バッファーにアタッチされたステンシル バッファーを列挙します。
        /// 列挙子中に <c>null</c> は含まれません
        /// </remarks>
        /// <seealso cref="StencilBufferCount"/>
        public IEnumerable<Image2D> StencilBuffers {
            get { return stencilBuffers.Where (x => x != null); }
        }

        /// <summary>
        /// カラー バッファー画像
        /// </summary>
        /// <remarks>
        /// このフレーム バッファーにアタッチされたカラー バッファー画像のうち0番目のものを返します。
        /// 0番目のカラー バッファーに画像がアタッチされていない場合 <c>null</c> が返ります。
        /// このプロパティは単一のカラー バッファーを持つフレーム バッファーの為に用意されています。
        /// i番目のカラー バッファーを取得するには <see cref="GetColorBuffer()"/> を利用してください。
        /// </remarks>
        /// <seealso cref="GetColorBuffer"/>
        public Image2D ColorBuffer {
            get { return colorBuffers.ElementAt (0); }
        }

        /// <summary>
        /// デプス バッファー画像
        /// </summary>
        /// <remarks>
        /// このフレーム バッファーにアタッチされたデプス バッファー画像を返します。
        /// デプスバッファーに画像がアタッチされていない場合 <c>null</c> が返ります。
        /// </remarks>
        public Image2D DepthBuffer {
            get { return depthBuffers.ElementAt (0); }
        }

        /// <summary>
        /// ステンシル バッファー画像
        /// </summary>
        /// <remarks>
        /// このフレーム バッファーにアタッチされたステンシル バッファー画像を返します。
        /// ステンシル バッファーに画像がアタッチされていない場合 <c>null</c> が返ります。
        /// </remarks>
        public Image2D StencilBuffer {
            get { return stencilBuffers.ElementAt (0); }
        }

        /// <inheritdoc/>
        public override IEnumerable<Object> References {
            get {
                var objs = base.References.ToList ();
                objs.AddRange (colorBuffers);
                objs.AddRange (depthBuffers);
                objs.AddRange (stencilBuffers);
                objs.RemoveAll (x => x == null);
                return objs;
            }
        }
        #endregion

        #region Method
        /// <summary>
        /// カラー バッファー インデックスの取得
        /// </summary>
        /// <remarks>
        /// 指定の画像オブジェクト <paramref name="img"/> がアタッチされたインデックスを取得します。
        /// アタッチされていなかった場合例外が発生します。
        /// </remarks>
        /// <param name="img">画像オブジェクト</param>
        /// <exception cref="ArgumentNullException"><paramref name="img"/> が <c>null</c></exception>
        /// <exception cref="ArgumentException"><paramref name="img"/> が <see cref="ColorBuffers"/> に含まれていない</exception>
        /// <returns>インデックス</returns>
        public int GetColorBufferIndex (Image2D img) {
            if (img == null) {
                throw new ArgumentNullException ("Img is null");
            }
            if (!colorBuffers.Contains (img)) {
                throw new ArgumentException ("Img is not in the ColorBuffers");
            }
            return Array.IndexOf (colorBuffers, img);
        }

        /// <summary>
        /// カラー バッファー画像の取得
        /// </summary>
        /// <remarks>
        /// i番目のカラー バッファーにアタッチされた画像を取得します。
        /// </remarks>
        /// <param name="index">カラーバッファー番号</param>
        /// <exception cref="IndexOutOfRangeException"><paramref name="index"/> &lt; 0 または <paramref name="index"/> &gt;= <see cref="ColorBufferCount"/></exception>
        /// <returns>2次元画像オブジェクト、または <c>null</c></returns>
        /// <seealso cref="ColorBuffer"/>
        public Image2D GetColorBuffer (int index) {
            if (index < 0 || index >= ColorBufferCount) {
                throw new IndexOutOfRangeException ("Indx is out of range");
            }
            return colorBuffers[index];
        }

        /// <summary>
        /// デプス バッファー画像の取得
        /// </summary>
        /// <remarks>
        /// デプス バッファーにアタッチされた画像を取得します。
        /// </remarks>
        /// <returns>2次元画像オブジェクト、または <c>null</c></returns>
        /// <seealso cref="DepthBuffer"/>
        public Image2D GetDepthBuffer () {
            if (DepthBufferCount <= 0) {
                throw new IndexOutOfRangeException ("No depth buffer");
            }
            return depthBuffers[0];
        }

        /// <summary>
        /// ステンシル バッファー画像の取得
        /// </summary>
        /// <remarks>
        /// ステンシル バッファーにアタッチされた画像を取得します。
        /// <note type="implement">
        /// ステンシル バッファーは実装されていません。ごめん
        /// </note>
        /// </remarks>
        /// <returns>2次元画像オブジェクト、または <c>null</c></returns>
        /// <seealso cref="StencilBuffer"/>
        public Image2D GetStencilBuffer () {
            throw new NotImplementedException ("Sorry");
            //if (StencilBufferCount <= 0) {
            //    throw new IndexOutOfRangeException ("No stencil buffer");
            //}
            //return stencilBuffers[0];
        }

        /// <summary>
        /// カラーバッファー画像の登録
        /// </summary>
        /// <remarks>
        /// i番目のカラー バッファーで使用する2次元画像オブジェクトを登録します。
        /// 
        /// <note type="implement">
        /// カラー バッファーとして使用できない可能性が高い圧縮系フォーマットは、
        /// ここでチェックして例外を発生するべきではないか？
        /// OpenGL の仕様として明確に禁止されてはいなかったと記憶しているが、
        /// 対応しているGPUはないはず（たぶん）。
        /// </note>
        /// </remarks>
        /// <param name="index">カラーバッファー番号</param>
        /// <param name="img">2次元画像オブジェクト</param>
        /// <exception cref="IndexOutOfRangeException"><paramref name="index"/> & lt; 0 または <paramref name="index"/> &gt; <see cref="ColorBufferCount"/></exception>
        public void SetColorBuffer (int index, Image2D img) {
            if (index < 0 || index >= ColorBufferCount) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            if (img != null && img.PixelFormat == PixelFormat.Depth) {
                throw new InvalidOperationException ("Depth image is invalid for ColorBuffer");
            }
            this.colorBuffers[index] = img;
        }

        /// <summary>
        /// デプス バッファー画像の登録
        /// </summary>
        /// <remarks>
        /// デプス バッファーとして使用する2次元画像オブジェクトを登録します。
        /// デプス バッファーにはフォーマットが <see cref="PixelFormat.Depth"/> の2次元画像のみがセット可能です｡
        /// これ以外のフォーマットを登録しようとすると例外が発生します。
        /// </remarks>
        /// <param name="img">2次元画像</param>
        /// <exception cref="InvalidOperationException"><see cref="DepthBufferCount"/> = 0</exception>
        /// <exception cref="ArgumentException"><paramref name="img"/>.<see cref="Image2D.PixelFormat"/> = <see cref="PixelFormat.Depth"/></exception>
        public void SetDepthBuffer (Image2D img) {
            if (DepthBufferCount == 0) {
                throw new InvalidOperationException ("Depth buffer is not enabled");
            }
            if (img.PixelFormat != PixelFormat.Depth) {
                throw new ArgumentException ("PixelFormat is invalid");
            }
            this.depthBuffers[0] = img;
        }

        /// <summary>
        /// ステンシル バッファー画像の登録
        /// </summary>
        /// <remarks>
        /// ステンシル バッファーとして使用する2次元画像オブジェクトを登録します。
        /// <note type="implement">
        /// ステンシル バッファーは現在未実装です。ごめん
        /// </note>
        /// </remarks>
        /// <param name="img">2次元画像オブジェクト</param>
        /// <exception cref="NotImplementedException">このメソッドを呼び出す</exception>
        public void SetStencilBuffer (Image2D img) {
            throw new NotImplementedException ("Sorry");
            //this.stencilBuffers[0] = tex;
        }


        /// <summary>
        /// カラー バッファー クリアの有効･無効の取得
        /// </summary>
        /// <remarks>
        /// i番目のカラー バッファーのクリアの有効無効フラグを取得します。
        /// クリアに使われる値はアタッチされた画像が保管しています。
        /// </remarks>
        /// <param name="index">カラー バッファー番号</param>
        /// <exception cref="InvalidOperationException"><see cref="ColorBufferCount"/> = 0</exception>
        /// <exception cref="IndexOutOfRangeException"><paramref name="IndexOutOfRangeException"/><paramref name="index"/> &lt; 0 または <paramref name="index"/> &gt;= <see cref="ColorBufferCount"/></exception>
        /// <returns>有効なら <c>true</c>, そうでなければ <c>false</c></returns>
        /// <seealso cref="Image2D"/>
        public bool GetColorClear (int index) {
            if (ColorBufferCount == 0) {
                throw new InvalidOperationException ("No color buffer");
            }
            if (index < 0 || index >= ColorBufferCount) {
                throw new IndexOutOfRangeException ("Index is invalid");
            }
            return colorClears[index];
        }

        /// <summary>
        /// デプス クリアの有効･無効の取得
        /// </summary>
        /// <remarks>
        /// デプス バッファーのクリアの有効無効フラグを取得します。
        /// クリアに使用される値はアタッチされた画像が保管しています。
        /// </remarks>
        /// <exception cref="InvalidOperationException"><see cref="DepthBufferCount"/> = 0</exception>
        /// <returns>有効なら <c>true</c>, そうでなければ <c>false</c></returns>
        public bool GetDepthClear () {
            if (DepthBufferCount == 0) {
                throw new InvalidOperationException ("No depth buffer");
            }
            return depthClears[0];
        }

        /// <summary>
        /// ステンシル クリアの有効･無効の取得
        /// </summary>
        /// <remarks>
        /// ステンシル クリアの有効無効フラグを取得します。
        /// クリアに使用される値はアタッチされた画像が保管しています。
        /// <note type="implement">
        /// 現在ステンシル バッファーは実装されていません。
        /// </note>
        /// </remarks>
        /// <exception cref="InvalidOperationException"><see cref="StencilBufferCount"/> = 0</exception>
        /// <returns></returns>
        public bool GetStencilClear () {
            if (StencilBufferCount == 0) {
                throw new InvalidOperationException ("No stencil buffer");
            }
            return stencilClears[0];
        }

        /// <summary>
        /// バッファー クリアの設定の変更
        /// </summary>
        /// <remarks>
        /// バッファー クリアの設定を変更します。
        /// カラー バッファー クリアは単一の<c>bool</c> 値で指定し、すべてのカラー バッファーの設定が指定値に変更されます。
        /// </remarks>
        /// <param name="colorClear">カラー バッファーのクリアの有効･無効（一括指定）</param>
        /// <param name="depthClear">デプス バッファーのクリアの有効･無効</param>
        /// <param name="stencilClear">ステンシル バッファーのクリアの有効･無効</param>
        public void SetClear (bool colorClear, bool depthClear, bool stencilClear) {
            for (var i = 0; i < ColorBufferCount; i++) {
                this.colorClears[i] = colorClear;
            }
            for (var i = 0; i < DepthBufferCount; i++) {
                this.depthClears[i] = depthClear;
            }
            for (var i = 0; i < StencilBufferCount; i++) {
                this.stencilClears[i] = stencilClear;
            }
        }

        /// <summary>
        /// バッファー クリアの設定の変更
        /// </summary>
        /// <remarks>
        /// バッファー クリアの設定を変更します。
        /// カラー バッファー クリアは <c>bool</c> 値の配列で指定し、カラー バッファーの設定はそれぞれの値に変更されます。
        /// </remarks>
        /// <param name="colorClears">カラー バッファーのクリアの有効･無効（個別指定）</param>
        /// <param name="depthClear">デプス バッファーのクリアの有効･無効</param>
        /// <param name="stencilClear">ステンシル バッファーのクリアの有効･無効</param>
        /// <exception cref="ArgumentNullException"><paramref name="colorClears"/> が <c>null</c></exception>
        /// <exception cref="ArgumentException"><paramref name="colorClears"/>.<see cref="Array.Length"/> &lt; <see cref="ColorBufferCount"/></exception>
        public void SetClear (bool[] colorClears, bool depthClear, bool stencilClear) {
            if (colorClears == null) {
                throw new ArgumentNullException ("ColorClears is null");
            }
            if (colorClears.Length < ColorBufferCount) {
                throw new ArgumentException ("ColorClears is too short");
            }

            for (var i = 0; i < ColorBufferCount; i++) {
                this.colorClears[i] = colorClears[i];
            }
            for (var i = 0; i < DepthBufferCount; i++) {
                this.depthClears[i] = depthClear;
            }
            for (var i = 0; i < StencilBufferCount; i++) {
                this.stencilClears[i] = stencilClear;
            }
        }

        /// <inheritdoc/>
        public override string ToString () {
            return string.Format ("FrameBuffer: {0}, {1}, {2}", ColorBufferCount, DepthBufferCount, StencilBufferCount);
        }
        #endregion
    }
}
